print("hello,ovo")
local properties, system
local depot = peripheral.find("create:depot")
if not depot then printError("No depot") return end
system = {
    fileName = "cbc_dat", 
    file = nil,      
}
system.init = function()
    system.file = io.open(system.fileName, "r")
    if system.file then
        local tmpProp = textutils.unserialise(system.file:read("a"))
        properties = system.reset()
        for k, v in pairs(properties) do
            if tmpProp[k] then
                properties[k] = tmpProp[k]
            end
        end
        system.file:close()
    else
        properties = system.reset()
        system.updatePersistentData()
    end
end

system.reset = function()
    return {
    offset = 1,
    speed = 20.0,
    count = 1,
    faces = "up",
    mobType = "cbc",
    Entity = "he",
    INPUT = "top",
    fuze = "impact",
    number = 0,
    Coolingtime = 3,
    light = false,
    Shield = false,
    smoke =false,
    r = 5,
    pass = "ovo",
    scattering = false,
    scatter = 0.025
    }
end

system.updatePersistentData = function()
    system.write(system.fileName, properties)
end

system.write = function(file, obj)
    system.file = io.open(file, "w")
    system.file:write(textutils.serialise(obj))
    system.file:close()
end

system.init()

commands.execAsync(string.format(
"/gamerule commandBlockOutput false"
))

local fuze = {
    time = function(num) return ("{id:\"createbigcannons:timed_fuze\",tag:{FuzeTimer:%d},Count:1b}"):format(num) end,
    proximity = function(num) return ("{id:\"createbigcannons:proximity_fuze\",tag:{DetonationDistance:%d},Count:1b}"):format(num) end,
    delayed = function(num) return ("{id:\"createbigcannons:delayed_impact_fuze\",tag:{FuzeTimer:%d},Count:1b}"):format(num) end,
    impact = function() return "{id:\"createbigcannons:impact_fuze\",Count:1b}" end
}

local name = "createbigcannons:he_shell"
local getname = function ()
    while true do
        local item = depot.getItemDetail(1)
        term.setCursorPos(2, 10)
        if not item then printError("No item") name = "createbigcannons:he_shell" else name = item.name end
        term.setCursorPos(2, 11)
        printError(("%s"):format(name))
        sleep(0.5)
    end
end
local vector = {}
local newVec = function (x, y, z)
    if type(x) == "table" then
        return setmetatable({ x = x.x, y = x.y, z = x.z}, { __index = vector })
    elseif x and y and z then
        return setmetatable({ x = x, y = y, z = z}, { __index = vector})
    else
        return setmetatable({ x = 0, y = 0, z = 0}, { __index = vector})
    end
end

function vector:zero()
    self.x = 0
    self.y = 0
    self.z = 0
    return self
end

function vector:copy()
    return newVec(self.x, self.y, self.z)
end
local square = function (num)
    return num * num
end
function vector:len()
    return math.sqrt(square(self.x) + square(self.y) + square(self.z) )
end

function vector:time()
    local l = self:len()
    if l == 0 then
        self:zero()
    else
        self.x = self.x / l
        self.y = self.y / l
        self.z = self.z / l
    end
    return self
end

function vector:nega()
    self.x = -self.x
    self.y = -self.y
    self.z = -self.z
    return self
end

function vector:add(v)
    self.x = self.x + v.x
    self.y = self.y + v.y
    self.z = self.z + v.z
    return self
end

function vector:sub(v)
    self.x = self.x - v.x
    self.y = self.y - v.y
    self.z = self.z - v.z
    return self
end

function vector:scale(num)
    self.x = self.x * num
    self.y = self.y * num
    self.z = self.z * num
    return self
end

function vector:unpack()
    return self.x, self.y, self.z
end

local quat = {}
function quat.new()
    return { w = 1, x = 0, y = 0, z = 0}
end

function quat.vecRot(q, v)
    local x = q.x * 2
    local y = q.y * 2
    local z = q.z * 2
    local xx = q.x * x
    local yy = q.y * y
    local zz = q.z * z
    local xy = q.x * y
    local xz = q.x * z
    local yz = q.y * z
    local wx = q.w * x
    local wy = q.w * y
    local wz = q.w * z
    local res = {}
    res.x = (1.0 - (yy + zz)) * v.x + (xy - wz) * v.y + (xz + wy) * v.z
    res.y = (xy + wz) * v.x + (1.0 - (xx + zz)) * v.y + (yz - wx) * v.z
    res.z = (xz - wy) * v.x + (yz + wx) * v.y + (1.0 - (xx + yy)) * v.z
    return newVec(res.x, res.y, res.z)
end

function quat.multiply(q1, q2)
    local newQuat = {}
    newQuat.w = -q1.x * q2.x - q1.y * q2.y - q1.z * q2.z + q1.w * q2.w
    newQuat.x = q1.x * q2.w + q1.y * q2.z - q1.z * q2.y + q1.w * q2.x
    newQuat.y = -q1.x * q2.z + q1.y * q2.w + q1.z * q2.x + q1.w * q2.y
    newQuat.z = q1.x * q2.y - q1.y * q2.x + q1.z * q2.w + q1.w * q2.z
    return newQuat
end

function quat.nega(q)
    return {
        w = q.w,
        x = -q.x,
        y = -q.y,
        z = -q.z,
    }
end
local face = {
    up = newVec(0, 1, 0),
    down = newVec(0, -1, 0),
    north = newVec(0, 0, -1),
    south = newVec(0, 0, 1),
    west = newVec(-1, 0, 0),
    east = newVec(1, 0, 0)
}

local block_offset = newVec(0.5, 0.5, 0.5)
local selfPos = newVec(coordinate.getAbsoluteCoordinates())
local getcbcGenPos = function()
    local face_offset = newVec(face[properties.faces]):scale(properties.offset)
    local wPos = newVec(ship.getWorldspacePosition())
    local yardPos = newVec(ship.getShipyardPosition())
    local offset = yardPos:sub(selfPos):sub(block_offset):sub(face_offset)
    offset = quat.vecRot(ship.getQuaternion(), offset)
    return wPos:sub(offset)
end

local getShieldPos = function()
    local yardPos = newVec(ship.getShipyardPosition())
    local selfPos = newVec(coordinate.getAbsoluteCoordinates())
    local wPos = newVec(ship.getWorldspacePosition())
    local shieldpos = yardPos:sub(selfPos)
    return wPos:sub(shieldpos)
end

local getUpDirection = function()
    local up_vec = face[properties.faces]
    return quat.vecRot(ship.getQuaternion(), up_vec)
end

local createLaserBeamEffect = function(startX, startY, startZ, direction, length)
    local particleDensity = 4
    local totalParticles = math.floor(length * particleDensity)
    local lengtha = length/2
    for i = 0, totalParticles do
        local progress = i / totalParticles
        local posX = startX + direction.x  * progress * lengtha
        local posY = startY + direction.y  * progress * lengtha
        local posZ = startZ + direction.z  * progress * lengtha
            commands.execAsync(string.format(
                "/particle createbigcannons:cannon_smoke 1 1 10 0.5 %0.2f %0.2f %0.2f %0.2f %0.2f %0.2f 0 0 force",
                posX, posY, posZ , posX, posY, posZ
            ))
        if i % 2 == 0 then
            commands.execAsync(string.format(
                "/particle minecraft:end_rod %0.2f %0.2f %0.2f %.2f %.2f %.2f 0.1 30 force",
                posX, posY, posZ,
                direction.x * 0.05,  
                direction.y * 0.05,
                direction.z * 0.05
            ))
        end
    end
end

local Cooling_time = function ()
    for i = properties.Coolingtime, 1, -1 do
        term.setCursorPos(20, 7)
        printError(string.format("%d", i))
        sleep(1)
    end
    term.setCursorPos(20, 7)
   printError("   ")
end

local genUpwardcbc = function()
    local pro
    if string.find(name, "ap_shell") then
        pro = 50
    elseif string.find(name, "apfs") or string.find(name, "apds") then
        pro = 500
    elseif string.find(name, "ap") then
        pro = 200
    else
        pro = 1
    end
    local fuzeStr = fuze[properties.fuze](properties.number)
    local base_speed = properties.speed / 20 
    local spawn_pos = getcbcGenPos()
    local up_dir = getUpDirection():time()
    local scatter = properties.scatter
    createLaserBeamEffect(spawn_pos.x, spawn_pos.y, spawn_pos.z, up_dir, base_speed)
    if properties.count < 1 then properties.count = 1 end
    local count = properties.count
    for i = 1, count, 1 do
        local current_speed = base_speed
        local current_dir = up_dir:copy()  
        if properties.scattering then
            local speed_variation = 1 + (math.random() * 2 - 1) * scatter
            current_speed = current_speed * speed_variation
            local scatter_strength = scatter 
            current_dir.x = current_dir.x + (math.random() * 2 - 1) * scatter_strength
            current_dir.y = current_dir.y + (math.random() * 2 - 1) * scatter_strength
            current_dir.z = current_dir.z + (math.random() * 2 - 1) * scatter_strength
            local length = math.sqrt(current_dir.x^2 + current_dir.y^2 + current_dir.z^2)
            if length > 0 then
                current_dir.x = current_dir.x / length
                current_dir.y = current_dir.y / length
                current_dir.z = current_dir.z / length
            end
        end
        
        local motion_vec = current_dir:scale(current_speed)
        local str_pos = string.format("%0.2f %0.2f %0.2f", spawn_pos.x, spawn_pos.y, spawn_pos.z)
        local motion_str = string.format("Motion:[%.4fd,%.4fd,%.4fd]", motion_vec.x, motion_vec.y, motion_vec.z)
        local summon_cmd = ('/summon %s %s {Tags:["%s"],ProjectileMass:%d,Damage:100,%s,Fuze:%s,Tracer:{Count:1b,id:\"createbigcannons:tracer_tip\"},Age:5000}'):format(
            name, str_pos, properties.pass, pro, motion_str, fuzeStr)
        commands.execAsync(summon_cmd)
    end
    commands.execAsync(string.format(
        "/playsound createbigcannons:fire_big_cannon block @a %d %d %d 32 1 0.1",
        spawn_pos.x, spawn_pos.y, spawn_pos.z))
    Cooling_time()
end


local redstoneControl = function()
    while true do
        if redstone.getInput(properties.INPUT) == true then
            genUpwardcbc()
        end
        sleep(0.05)
    end
end

local function ShowShield()
    local mova = 0
    while true do
        if properties.Shield == true and math.abs(mova) < 1 then
            local Shieldpos = getShieldPos()
            local r = properties.r or 5  
            local time = os.clock() * 1
            local pitchAngle = math.sin(time * 0.8) * math.pi / 2.1
            for i = 0, 360, 15 do
                local horizontalAngle = math.rad(i + time * 20)
                local x = math.cos(horizontalAngle) * r * math.cos(pitchAngle)
                local z = math.sin(horizontalAngle) * r * math.cos(pitchAngle)
                local y = math.sin(pitchAngle) * r
                
                commands.execAsync(string.format(
                    "/particle minecraft:end_rod %.2f %.2f %.2f 0 0 0 0 1 force",
                    Shieldpos.x + x, Shieldpos.y + y, Shieldpos.z + z
                ))
            end
        end
        sleep(0.05)  
        local move = ship.getVelocity()
        mova = move.x + move.y + move.z
    end
end
local Shield = function ()
    while true do
        if properties.Shield == true then
            local Shieldpos = getShieldPos()
            local spawn_pos = getcbcGenPos()
            local pass = properties.pass
            local r = properties.r
            commands.execAsync(string.format(
            "/execute positioned %0.2f %0.2f %0.2f run tag @e[distance=..4,nbt={HasBeenShot:1b}] add %s",
            spawn_pos.x, spawn_pos.y, spawn_pos.z, pass
            ))
            commands.execAsync(string.format(
            "/execute positioned %0.2f %0.2f %0.2f as @a[distance=..%d] at @s run tag @e[nbt={HasBeenShot:1b},distance=..5] add %s",
            spawn_pos.x, spawn_pos.y, spawn_pos.z, r , pass
            ))
            commands.execAsync(string.format(
            "/execute positioned %0.2f %0.2f %0.2f as @e[tag=!%s,nbt={HasBeenShot:1b},nbt={OnGround:false},distance=..%d] at @s run particle minecraft:sonic_boom ~ ~ ~ 0 0 0 0 2 force",
            Shieldpos.x, Shieldpos.y, Shieldpos.z, pass, r
            ))
            commands.execAsync(string.format(
            "/execute positioned %0.2f %0.2f %0.2f as @e[tag=!%s,nbt={HasBeenShot:1b},nbt={OnGround:false},distance=..%d] at @s run playsound minecraft:block.beacon.activate block @a ~ ~ ~ 16 1.5 0",
            Shieldpos.x, Shieldpos.y, Shieldpos.z, pass, r
            ))
            commands.execAsync(string.format(
            "/execute positioned %0.2f %0.2f %0.2f as @e[tag=!%s,nbt={HasBeenShot:1b},distance=..%d] run kill @s",
            Shieldpos.x, Shieldpos.y, Shieldpos.z, pass, r
            ))
        end
        sleep(0.05)
    end
end

local light = function ()
    while true do
        if properties.light == true then
            commands.execAsync(string.format(
            "/execute as @e[tag=ovo,nbt=!{InGround:1b}] at @s run particle minecraft:campfire_signal_smoke ~ ~ ~ 0.1 0.1 0.1 0 5 force"
            ))
        end
    sleep(0.05)
    end
end
local smoke = function ()
    while true do
        if properties.smoke == true then
            local Shieldpos = getShieldPos()
            local r = properties.r
            commands.execAsync(string.format(
                "/execute positioned %.2f %.2f %.2f run particle createbigcannons:gas_cloud 10 10 10 5 ~ ~ ~ %.1f %.1f %.1f 0 %d force @a[distance=%d..]",
                Shieldpos.x, Shieldpos.y, Shieldpos.z, r, r, r ,r*5, r
            ))
            sleep(2)
        end
        sleep(0.05)
    end
end

---------------- UI 界面 -----------------
local termUtil = {
    cpX = 1,
    cpY = 1,
}

local absTextField = {
    x = 1,
    y = 1,
    len = 15,
    text = "",
    textCorlor = "0",
    backgroundColor = "8",
}

local genStr = function(s, count)
    local result = ""
    for i = 1, count, 1 do
        result = result .. s
    end
    return result
end

function absTextField:paint()
    local str = ""
    for i = 1, self.len, 1 do
        local text = tostring(self.key[self.value])
        local tmp = string.sub(text, i, i)
        if #tmp > 0 then
            str = str .. tmp
        else
            local tmp2 = ""
            for j = 0, self.len - i, 1 do
                tmp2 = tmp2 .. " "
            end
            str = str .. tmp2
            break
        end
    end

    term.setCursorPos(self.x, self.y)
    term.blit(str, genStr(self.textCorlor, #str), genStr(self.backgroundColor, #str))
end

function absTextField:inputChar(char)
    local xPos, yPos = term.getCursorPos()
    xPos = xPos + 1 - self.x
    local field = tostring(self.key[self.value])
    if #field < self.len then
        if self.type == "number" then
            if char >= '0' and char <= '9' then
                if field == "0" then
                    field = char
                else
                    field = string.sub(field, 1, xPos) .. char .. string.sub(field, xPos, #field)
                end

                self.key[self.value] = tonumber(field)
                termUtil.cpX = termUtil.cpX + 1
            end
            if char == '-' then
                self.key[self.value] = -self.key[self.value]
            end
        elseif self.type == "string" then
            local strEnd = string.sub(field, xPos, #field)
            field = string.sub(field, 1, xPos - 1) .. char .. strEnd
            self.key[self.value] = field
            termUtil.cpX = termUtil.cpX + 1
        end
    end
end

function absTextField:inputKey(key)
    local xPos, yPos = term.getCursorPos()
    local field = tostring(self.key[self.value])
    local minXp = self.x
    local maxXp = minXp + #field
    if key == 259 or key == 261 then --backSpace
        if xPos > minXp then
            termUtil.cpX = termUtil.cpX - 1
            if #field > 0 and termUtil.cpX > 1 then
                local index = termUtil.cpX - self.x
                field = string.sub(field, 1, index) .. string.sub(field, index + 2, #field)
            end
            if self.type == "number" then
                local number = tonumber(field)
                if not number then
                    self.key[self.value] = 0
                else
                    self.key[self.value] = number
                end
            elseif self.type == "string" then
                self.key[self.value] = field
            end
        end
    elseif key == 257 or key == 335 then
        --print("enter")
    elseif key == 262 or key == 263 then
        if key == 262 then
            termUtil.cpX = termUtil.cpX + 1
        elseif key == 263 then
            termUtil.cpX = termUtil.cpX - 1
        end
    elseif key == 264 or key == 258 then
        --print("down")
    elseif key == 265 then
        --print("up")
    end
    termUtil.cpX = termUtil.cpX > maxXp and maxXp or termUtil.cpX
    termUtil.cpX = termUtil.cpX < minXp and minXp or termUtil.cpX
end

function absTextField:click(x, y)
    local xPos = self.x
    if x >= xPos then
        if x < xPos + #tostring(self.key[self.value]) then
            termUtil.cpX, termUtil.cpY = x, y
        else
            termUtil.cpX, termUtil.cpY = xPos + #tostring(self.key[self.value]), y
        end
    end
end

local newTextField = function(key, value, x, y)
    return setmetatable({ key = key, value = value, type = type(key[value]), x = x, y = y },
        { __index = absTextField })
end

local absSelectBox = {
    x = 1,
    y = 1,
    label = "",
    contents = {},
    count = 0,
    interval = 0,
    fontColor = "8",
    backgroundColor = "f",
    selectColor = "e"
}

function absSelectBox:paint()
    term.setCursorPos(self.x, self.y)
    local select = tostring(self.key[self.value])
    for i = 1, #self.contents, 1 do
        local str = tostring(self.contents[i])
        if select == str then
            term.blit(str, genStr(self.backgroundColor, #str), genStr(self.selectColor, #str))
        else
            term.blit(str, genStr(self.fontColor, #str), genStr(self.backgroundColor, #str))
        end
        for j = 1, self.interval, 1 do
            term.write(" ")
        end
    end
end


function absSelectBox:click(x, y)
    local xPos = x - self.x + 1
    local index = 0
    
    for i = 1, #self.contents do
        local item = tostring(self.contents[i])
        if xPos >= index and xPos <= index + #item then
            self.key[self.value] = self.contents[i]
            termUtil.cpX, termUtil.cpY = self.x, self.y
            term.setCursorPos(termUtil.cpX, termUtil.cpY)
            term.setCursorBlink(false)  
            break
        end
        index = index + #item + self.interval
    end
end

local newSelectBox = function(key, value, interval, x, y, ...)
    return setmetatable(
        { key = key, value = value, interval = interval, x = x, y = y, type = type(key[value]), contents = { ... } },
        { __index = absSelectBox })
end

local absSlider = {
    x = 1,
    y = 1,
    min = 0,
    max = 1,
    len = 20,
    fontColor = "8",
    backgroundColor = "f"
}


function absSlider:paint()
    local field = self.key[self.value]
    if field == "-" then
        field = 0
    end
    local maxVal = self.max - self.min
    local xPos = math.floor((field - self.min) * (self.len / maxVal) + 0.5)
    xPos = xPos < 1 and 1 or xPos
    term.setCursorPos(self.x, self.y)
    for i = 1, self.len, 1 do
        if xPos == i then
            term.blit(" ", self.backgroundColor, self.fontColor)
        else
            term.blit("-", self.fontColor, self.backgroundColor)
        end
    end
end


function absSlider:click(x, y)
    local xPos = x + 1 - self.x
    if xPos > self.len then
        xPos = self.len
    end
    self.key[self.value] = (self.max - self.min) * (xPos / self.len) + self.min
end

-- 新建滑块
local newSlider = function(key, value, min, max, len, x, y)
    return setmetatable({ key = key, value = value, min = min, max = max, len = len, x = x, y = y },
        { __index = absSlider })
end

local runTerm = function()
        local fieldTb = {
            offset = newTextField(properties, "offset", 12, 4),
            speed = newTextField(properties, "speed", 12, 6),
            count = newTextField(properties, "count", 36, 6),
            number = newTextField(properties, "number", 2, 12),
            Coolingtime = newTextField(properties, "Coolingtime", 15, 7),
            r = newTextField(properties, "r", 27, 19),
            pass = newTextField(properties, "pass", 37, 19),
            scatter = newTextField(properties, "scatter", 40, 16)
        }
        fieldTb.offset.len = 2
        fieldTb.speed.len = 5
        fieldTb.count.len = 2
        fieldTb.number.len = 3
        fieldTb.Coolingtime.len = 3
        fieldTb.r.len = 3
        fieldTb.pass.len = 10
        fieldTb.scatter.len = 5        
        local selectBoxTb = {
            faces = newSelectBox(properties, "faces", 2, 10, 9, "up", "down", "north", "south", "west", "east"),
            INPUT = newSelectBox(properties, "INPUT", 2, 8, 14, "top", "left", "right", "front", "back","bottom"),
            fuze = newSelectBox(properties, "fuze", 2, 10, 12, "time", "proximity", "delayed", "impact"),
            light = newSelectBox(properties, "light", 2, 8, 17, true, false),
            smoke = newSelectBox(properties, "smoke", 2, 8, 18, true, false),
            Shield = newSelectBox(properties, "Shield", 2, 10, 19, true, false),
            scattering = newSelectBox(properties, "scattering", 2, 13, 15, true, false)
        }
        local sliderTb = {
            scatter = newSlider(properties, "scatter", 0 , 0.1 , 25 , 25, 15)
        }
        local alarm_flag = false
        term.clear()
        term.setCursorPos(15, 8)
        term.write("Press any key to continue")
        while true do
            local eventData = { os.pullEvent() }
            local event = eventData[1]
            if event == "mouse_up" or event == "key_up" or event == "alarm"
                or event == "mouse_click" or event == "mouse_drag" or event == "key" or event == "char" then
                if not alarm_flag then
                    alarm_flag = true
                else
                    term.clear()
                    term.setCursorPos(10, 2)
                    term.write("====----cannon Control----====")
                    term.setCursorPos(2, 4)
                    term.write("Offset:")
                    term.setCursorPos(2, 6)
                    term.write("Speed:")
                    term.setCursorPos(30, 6)
                    term.write("Count:")
                    term.setCursorPos(2, 7)
                    term.write("Coolingtime:")
                    term.setCursorPos(2, 9)
                    term.write("Facing:")
                    term.setCursorPos(2, 14)
                    term.write("Input:")
                    term.setCursorPos(2, 15)
                    term.write("scattering:")
                    term.setCursorPos(2, 17)
                    term.write("Light:")
                    term.setCursorPos(2, 18)
                    term.write("Smoke:")
                    term.setCursorPos(2, 19)
                    term.write("Shield:")
                    term.setCursorPos(24, 19)
                    term.write("r:")
                    term.setCursorPos(31, 19)
                    term.write("pass:")
                    for k, v in pairs(fieldTb) do
                        v:paint()
                    end

                    for k, v in pairs(selectBoxTb) do
                        v:paint()
                    end
                    for k, v in pairs(sliderTb) do
                        v:paint()
                    end
                    term.setCursorPos(termUtil.cpX, termUtil.cpY)

                    if event == "mouse_click" then
                    term.setCursorBlink(true)
                    local x, y = eventData[3], eventData[4]
                    for k, v in pairs(fieldTb) do
                        if y == v.y and x >= v.x and x <= v.x + v.len then
                            v:click(x, y)
                        end
                    end
                    for k, v in pairs(selectBoxTb) do
                        if y == v.y then
                            v:click(x, y)
                        end
                    end
                    for k, v in pairs(sliderTb) do
                        if y == v.y then
                            v:click(x, y)
                        end
                    end
                elseif event == "mouse_drag" then
                    local x, y = eventData[3], eventData[4]
                    for k, v in pairs(sliderTb) do
                        if y == v.y then
                            v:click(x, y)
                        end
                    end
                elseif event == "key" then
                    local key = eventData[2]
                    for k, v in pairs(fieldTb) do
                        if termUtil.cpY == v.y and termUtil.cpX >= v.x and termUtil.cpX <= v.x + v.len then
                            v:inputKey(key)
                        end
                    end
                elseif event == "char" then
                    local char = eventData[2]
                    for k, v in pairs(fieldTb) do
                        if termUtil.cpY == v.y and termUtil.cpX >= v.x and termUtil.cpX <= v.x + v.len then
                            v:inputChar(char)
                        end
                    end
                end
                system.updatePersistentData()
            end
        end
    end
end

local ability = function ()
    parallel.waitForAll(light,Shield,smoke,ShowShield)
end

parallel.waitForAll(redstoneControl, runTerm, ability,getname)
parallel.waitForAll(redstoneControl, runTerm, ability,getname)
